<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="qPqzL" id="qPqzL"><span data-lake-id="ud551e178" id="ud551e178">典型回答</span></h1>
  <p data-lake-id="u81b430f3" id="u81b430f3"><br></p>
  <p data-lake-id="u31b124b6" id="u31b124b6"><br></p>
  <p data-lake-id="u25f5cbbf" id="u25f5cbbf"><span data-lake-id="u37613c52" id="u37613c52">不懂什么是一锁二查三更新的，先看上面这个问题↑↑↑。</span></p>
  <p data-lake-id="u042955eb" id="u042955eb"><span data-lake-id="u8fc80c8e" id="u8fc80c8e">​</span><br></p>
  <p data-lake-id="u8ab77460" id="u8ab77460"><span data-lake-id="ue8d3c275" id="ue8d3c275">那么，用这个这个方式做幂等防控，就一定不会出现脏数据了么？先看下以下代码：</span></p>
  <p data-lake-id="u2a589be8" id="u2a589be8"><span data-lake-id="uc1f0c486" id="uc1f0c486">​</span><br></p>
  <pre lang="java"><code>
@Transactional(rollbackFor = Exception.class)
public boolean order(Request request) {

	RLock lock = redisson.getLock(request.getIdentifier());
    try {
        //一锁
        lock.lock();

        //二查
        OrderDo order = orderMapper.find(request.getIdentifier());
        if (order!=null) {
            resp.setResultFail(ReturnCodeEnum.USER_EXIST);
            return false;
        }

        //三更新，保存订单数据
        orderMapper.insertOrder(request);
        
    } finally {
        lock.unlock();
    }


    //三更新，保存订单流水
    orderStreamMapper.insertOrder(request);
    //三更新，保存事件流水
    eventMapper.insert(request);
    return true;
}
</code></pre>
  <p data-lake-id="ub56ea46d" id="ub56ea46d"><br></p>
  <p data-lake-id="ua780a1be" id="ua780a1be"><span data-lake-id="ue0805306" id="ue0805306">以上代码，其实是有可能出现脏数据的。</span></p>
  <p data-lake-id="u7b826ded" id="u7b826ded"><span data-lake-id="uf2f103cf" id="uf2f103cf">​</span><br></p>
  <p data-lake-id="ud8fc07c1" id="ud8fc07c1"><span data-lake-id="ua48c71e7" id="ua48c71e7">首先，我们可以看到，代码中通过@Transactional在order方法上增加了一个事务，事务的范围是整个方法，也就是方法全部执行完，事务才会提交。</span></p>
  <p data-lake-id="u63ba428d" id="u63ba428d"><span data-lake-id="u80e99c56" id="u80e99c56">​</span><br></p>
  <p data-lake-id="u134a5ca4" id="u134a5ca4"><span data-lake-id="u4adca07c" id="u4adca07c">也就是说，代码完第28行之后，事务才会提交。可是锁的释放，是在第20行就执行了。</span></p>
  <p data-lake-id="ua39f12a7" id="ua39f12a7"><span data-lake-id="udf426044" id="udf426044">​</span><br></p>
  <p data-lake-id="uf3b89b30" id="uf3b89b30"><span data-lake-id="ub69fef2a" id="ub69fef2a">那么就会出现一个问题，锁已经释放了，但是事务还没提交。这时候其他的线程在并发请求过来的时候：<br><br></span></p>
  <p data-lake-id="ub31e8e3c" id="ub31e8e3c"><span data-lake-id="uf36289cc" id="uf36289cc">一锁。拿锁可以拿到，因为锁被释放了</span></p>
  <p data-lake-id="ud30731b2" id="ud30731b2"><span data-lake-id="ue1df2fb4" id="ue1df2fb4">二查。查询数据也查不到，因为这时候之前的那个事务可能还没提交，未提交的数据，新的事务是看不到的。</span></p>
  <p data-lake-id="ubc55cae8" id="ubc55cae8"><span data-lake-id="u648630ab" id="u648630ab">三更新。执行更新操作，导致数据重复。</span></p>
  <p data-lake-id="u719c7245" id="u719c7245"><span data-lake-id="u6a91cd78" id="u6a91cd78">​</span><br></p>
  <p data-lake-id="u92204343" id="u92204343"><span data-lake-id="u275a696a" id="u275a696a">以上，</span><strong><span data-lake-id="ud75cd99e" id="ud75cd99e">就是因为事务的粒度大于锁的粒度，并且因为事务之间隔离性，导致并发了。</span></strong></p>
  <p data-lake-id="u76de27d9" id="u76de27d9"><strong><span data-lake-id="u8ac213d4" id="u8ac213d4">​</span></strong><br></p>
  <p data-lake-id="u793d9880" id="u793d9880"><span data-lake-id="u8e9eb180" id="u8e9eb180">​</span><br></p>
  <p data-lake-id="u48f0c336" id="u48f0c336"><span data-lake-id="u6bd20d16" id="u6bd20d16">想要解决这个问题，就是减小事务的粒度，或者增大锁的粒度，不要在事务提交前就把锁释放掉。</span></p>
  <p data-lake-id="u6e71cfcf" id="u6e71cfcf"><span data-lake-id="u01aa57d0" id="u01aa57d0">​</span><br></p>
  <p data-lake-id="u41a390df" id="u41a390df"><span data-lake-id="uec7304aa" id="uec7304aa">减小事务粒度，这时候就可以把声明式事务改成编程式事务，自己控制锁的粒度：</span></p>
  <p data-lake-id="u28645edc" id="u28645edc"><span data-lake-id="ub6a5ce80" id="ub6a5ce80">​</span><br></p>
  <pre lang="java"><code>
@Autowired
private TransactionTemplate transactionTemplate;

public boolean order(Request request) {

	RLock lock = redisson.getLock(request.getIdentifier());
    try {
        //一锁
        lock.lock();
        //二查
        OrderDo order = orderMapper.find(request.getIdentifier());
        if (order!=null) {
            resp.setResultFail(ReturnCodeEnum.USER_EXIST);
            return false;
        }

        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                //三更新，保存订单数据
            	orderMapper.insertOrder(request);
                //三更新，保存订单流水
                orderStreamMapper.insertOrder(request);
                //三更新，保存事件流水
                eventMapper.insert(request);  
            }
        });
    
    	return true;
    }finally {
        redisLock.unLock();
    }
}
</code></pre>
  <p data-lake-id="u9374d85d" id="u9374d85d"><br></p>
  <p data-lake-id="u74673c5c" id="u74673c5c"><span data-lake-id="u4a8a41fe" id="u4a8a41fe">​</span><br></p>
 </body>
</html>